using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FrameWork.Source.Texture;
using FrameWork.Source.Object_Classes;
using FrameWork.Source.Animation_Classes;
using Microsoft.Xna.Framework;
using Boomer.Source.Character;

namespace Boomer.Source.TiledMap
{
    class CTiledMap
    {
        #region delegate

        /// <summary>
        /// Delegate for collision checking on map
        /// </summary>
        /// <param name="character">Animation object on map</param>
        /// <param name="map">Map that object are on</param>
        public delegate void collisionChecking(CCharacter character , CTiledMap map);

        /// <summary>
        /// Delegate for deep layer updating on map
        /// </summary>
        /// <param name="pos">Position on map</param>
        /// <param name="map">Map that object are on</param>
        /// <returns></returns>
        public delegate float updateDeep(Vector2 pos , CTiledMap map);

        #endregion

        #region Enum

        /// <summary>
        /// Enum of map type
        /// </summary>
        public enum MAP_TYPE
        {
            GRASS = 0 ,
            ROAD = 1 ,
            LINE = 2 ,
            CROSS = 3 ,
            TREE = 4,
            HOUSE_YELLOW = 5,
            HOUSE_RED = 6,
            HOUSE_BLUE = 7,
            COUNT = 8 ,
            BOOM = 9 ,
            BOOM_FRAGMENT = 10 ,
            BREAK_BOX_RED = 11 ,
            BREAK_BOX_ORANGE = 12
        };

        #endregion

        #region Variables

        /// <summary>
        /// Holding map value 
        /// </summary>
        private int[][] m_pMap;

        /// <summary>
        /// Number of col this map has
        /// </summary>
        public int m_iMapWidth { get; private set; }

        /// <summary>
        /// Number of row this map has
        /// </summary>
        public int m_iMapHeight { get; private set; }

        /// <summary>
        /// Size of one cell in map
        /// </summary>
        public int m_iCellSize { get; set; }

        /// <summary>
        /// Position of this map in screen
        /// </summary>
        public Vector2 m_vecMapPos; 

        /// <summary>
        /// Layer image
        /// </summary>
        private CAnimatedObject m_pTiledLayer;

        public Vector2 playerPos { get; set; }

        #endregion

        #region Constructor

        public CTiledMap()
        {
            m_pMap = null;
            m_pTiledLayer = null;

            m_iMapWidth = 0;
            m_iMapHeight = 0;
            m_iCellSize = 0;

            m_vecMapPos = new Vector2(0, 0);
        }

        #endregion

        #region Initialize

        /// <summary>
        /// Init map value , size 
        /// </summary>
        /// <param name="numCol">Number of col for this map</param>
        /// <param name="numRow">Number of row for this map</param>
        /// <param name="cellSize">Size of one cell</param>
        public void initMap(int numCol, int numRow, int cellSize)
        {
            m_pMap = new int[numRow][];
            for (int i = 0; i < numRow; ++i)
            {
                m_pMap[i] = new int[numCol];
            }

            resetMap();

            m_iMapWidth = numCol;
            m_iMapHeight = numRow;
            m_iCellSize = cellSize;
        }

        /// <summary>
        /// Init tiled image for this map
        /// </summary>
        /// <param name="pImage">Tiled image</param>
        /// <param name="frameWidth">Frame width of the image</param>
        /// <param name="frameHeight">Frame height of the image</param>
        public void initTiledImage(CTexture pImage , int frameWidth , int frameHeight)
        {
            if (m_pTiledLayer != null)
            {
                m_pTiledLayer = null;
            }
            int count = (int)MAP_TYPE.COUNT;
            CAnimation[] pAnim;
            pAnim = new CAnimation[count];
            for (int i = 0; i < count; ++i)
            {
                pAnim[i] = new CAnimation();
                pAnim[i].createAnimation((ulong)i, i, i, i, 1.0f);
            }
            m_pTiledLayer = new CAnimatedObject();
            m_pTiledLayer.loadSprite(pImage, count, count, frameWidth, frameHeight, pAnim);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Set map position
        /// </summary>
        /// <param name="startPos">Starting position of this map</param>
        public void setMapPos(Vector2 startPos)
        {
            m_vecMapPos = startPos;
        }

        /// <summary>
        /// Set value of single cell
        /// </summary>
        /// <param name="row">Row index of that cell</param>
        /// <param name="col">Col index of that cell</param>
        /// <param name="type">Value in that cell</param>
        public void setCell(int row , int col , int type)
        {
            if (row == 7 && col == 4)
            {
                int a = 1;
            }
            if (row >= m_iMapHeight)
                return;
            if (col >= m_iMapWidth)
                return;

            m_pMap[row][col] = type;
        }

        /// <summary>
        /// Clear value of single cell
        /// </summary>
        /// <param name="row">Row index of that cell</param>
        /// <param name="col">Col index of that cell</param>
        public void clearCell(int row, int col)
        {
            if (row >= m_iMapHeight)
                return;
            if (col >= m_iMapWidth)
                return;

            m_pMap[row][col] = 0;
        }

        /// <summary>
        /// Get value of single cell
        /// </summary>
        /// <param name="row">Row index of that cell</param>
        /// <param name="col">Col index of that cell</param>
        /// <returns></returns>
        public int getCell(int row, int col)
        {
            if (row >= m_iMapHeight)
                return -1;
            if (col >= m_iMapWidth)
                return -1;

            return m_pMap[row][col];
        }

        /// <summary>
        /// Reset our map
        /// </summary>
        public void resetMap()
        {
            int i, j;

            for (i = 0; i < m_iMapHeight; ++i)
            {
                for (j = 0; j < m_iMapWidth; ++j)
                {
                    m_pMap[i][j] = 0;
                }
            }
        }

        /// <summary>
        /// Render our map into screen
        /// </summary>
        public void render()
        {
            int i, j;

            for (i = 0; i < m_iMapHeight; ++i)
            {
                for (j = 0; j < m_iMapWidth; ++j)
                {
                    if (m_pMap[i][j] >= (int)MAP_TYPE.COUNT)
                        m_pTiledLayer.changeAnimation(0);
                    else
                        m_pTiledLayer.changeAnimation(m_pMap[i][j]);
                    float posX = m_vecMapPos.X + j * m_iCellSize;
                    float posY = m_vecMapPos.Y + i * m_iCellSize;
                    m_pTiledLayer.m_vec2Position = new Vector2( posX, posY - 32);
                    if (m_pMap[i][j] == (int)MAP_TYPE.GRASS || m_pMap[i][j] >= (int)MAP_TYPE.COUNT)
                        m_pTiledLayer.m_fDeep = 0.0f;
                    else
                        m_pTiledLayer.m_fDeep = CTiledMap.updateDeepValue(new Vector2(posX, posY), this);
                    m_pTiledLayer.draw();
                }
            }
        }

        public Vector2 convertToMapPos(Vector2 pos)
        {
            Vector2 newPos = new Vector2();
            Vector2 mapPos = new Vector2();

            mapPos = convertToMapIndex(pos);

            if (mapPos.Y < 0)
                mapPos.Y = 0;
            if (mapPos.Y > m_iMapHeight)
                mapPos.Y = m_iMapHeight - 1;
            if (mapPos.X < 0)
                mapPos.X = 0;
            if (mapPos.X > m_iMapWidth)
                mapPos.X = m_iMapWidth - 1;

            newPos.X = m_vecMapPos.X + mapPos.Y * m_iCellSize;
            newPos.Y = m_vecMapPos.Y + mapPos.X * m_iCellSize;

            if (pos.X - newPos.X >= 40)
                newPos.X += m_iCellSize;
            if (pos.Y - newPos.Y >= 45)
                newPos.Y += m_iCellSize;

            return newPos;
        }

        public Vector2 convertToMapIndex(Vector2 pos)
        {
            Vector2 newPos = new Vector2();

            int varX = (int)m_vecMapPos.X / m_iCellSize;
            int varY = (int)m_vecMapPos.Y / m_iCellSize;
            int row = (int)pos.Y / m_iCellSize - varY;
            int col = ((int)pos.X + 1) / m_iCellSize - varX;

            newPos = new Vector2(row, col);

            return newPos;
        }

        /// <summary>
        /// Checking collision for this map
        /// </summary>
        /// <param name="rect">Object rectangle for collision checking</param>
        /// <returns></returns>
        public List<int> isCollision(Rectangle rect)
        {
            List<int> collisionIndex = new List<int>();
            int i, j;
            int varX = (int)m_vecMapPos.X / m_iCellSize;
            int varY = (int)m_vecMapPos.Y / m_iCellSize;

            int left = rect.Left / m_iCellSize - varX;
            int right = (rect.Right - 1) / m_iCellSize - varX;
            int top = rect.Top / m_iCellSize - varY;
            int bottom = (rect.Bottom - 1) / m_iCellSize - varY;

            for (i = top; i <= bottom; ++i)
                for (j = left; j <= right; ++j)
                {
                    if (i >= m_iMapWidth || j >= m_iMapHeight)
                        continue;
                    if (i < 0 || j < 0)
                        continue;
                    if (m_pMap[i][j] != 0)
                        collisionIndex.Add(m_pMap[i][j]);
                        //return m_pMap[i][j];
                }
            return collisionIndex;
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// Static method for collision checking
        /// </summary>
        /// <param name="character">Animated object that has collision on map</param>
        /// <param name="map">Map that our object are on</param>
        public static void collisionMap(CCharacter character , CTiledMap map)
        {
            if (character.m_isColliable)
            {
                List<int> collisionIndex = new List<int>();
                collisionIndex = map.isCollision(character.getBoundBox());
                int size = collisionIndex.Count;
                for (int i = 0; i < size; ++i)
                {
                    if (collisionIndex[i] != 0)
                    {
                        if (collisionIndex[i] == (int)MAP_TYPE.BOOM_FRAGMENT)
                        {
                            character.setDying();
                        }
                        else if (collisionIndex[i] == (int)MAP_TYPE.BOOM)
                        {
                            continue;
                        }
                        else
                        {
                            if (character.Tag == FrameWork.Tag_Manager.TagManager.TAGGED.Enemy)
                            {
                                ((CMob)character).changeDir();
                            }
                            character.m_vec2Position = character.m_vec2PrevPosition;
                            Vector2 mapPos = new Vector2(character.getBoundBox().X, character.getBoundBox().Y);
                            Vector2 convertPosition = map.convertToMapPos(mapPos);
                            Vector2 mapIndex = map.convertToMapIndex(convertPosition);
                            if (character.currDir == CCharacter.DIRECTION.BOT || character.currDir == CCharacter.DIRECTION.TOP)
                            {
                                bool moveBot = (character.currDir == CCharacter.DIRECTION.BOT) && (map.m_pMap[(int)mapIndex.X + 1][(int)mapIndex.Y] == 0);
                                bool moveTop = (character.currDir == CCharacter.DIRECTION.TOP) && (map.m_pMap[(int)mapIndex.X - 1][(int)mapIndex.Y] == 0);
                                if (moveBot || moveTop)
                                {
                                    if (convertPosition.X < mapPos.X)
                                        character.m_vec2Position -= new Vector2(1, 0);
                                    if (convertPosition.X > mapPos.X)
                                        character.m_vec2Position += new Vector2(1, 0);
                                }
                            }
                            if (character.currDir == CCharacter.DIRECTION.LEFT || character.currDir == CCharacter.DIRECTION.RIGHT)
                            {
                                bool moveLeft = (character.currDir == CCharacter.DIRECTION.LEFT) && (map.m_pMap[(int)mapIndex.X][(int)mapIndex.Y - 1] == 0);
                                bool moveRight = (character.currDir == CCharacter.DIRECTION.RIGHT) && (map.m_pMap[(int)mapIndex.X][(int)mapIndex.Y + 1] == 0);
                                if (moveLeft || moveRight)
                                {
                                    if (convertPosition.Y < mapPos.Y)
                                        character.m_vec2Position -= new Vector2(0, 1);
                                    if (convertPosition.Y > mapPos.Y)
                                        character.m_vec2Position += new Vector2(0, 1);
                                }
                            }
                        }
                    }
                }
            }
            bool isOutOfRangeTop = character.getBoundBox().Top <= map.m_vecMapPos.Y;
            bool isOutOfRangeBot = character.getBoundBox().Bottom >= (map.m_vecMapPos.Y + map.m_iMapHeight * map.m_iCellSize);
            bool isOutOfRangeLeft = character.getBoundBox().Left <= map.m_vecMapPos.X;
            bool isOutOfRangeRight = character.getBoundBox().Right >= (map.m_vecMapPos.X + map.m_iMapWidth * map.m_iCellSize);

            if (isOutOfRangeBot || isOutOfRangeTop || isOutOfRangeRight || isOutOfRangeLeft)
            {
                if (character.Tag == FrameWork.Tag_Manager.TagManager.TAGGED.Enemy)
                {
                    ((CMob)character).changeDir();
                }
                character.m_vec2Position = character.m_vec2PrevPosition;
            }
        }

        /// <summary>
        /// Static method for updating deep value
        /// </summary>
        /// <param name="pos">Input position</param>
        /// <param name="map">Map that our object are on</param>
        /// <returns>Deep value base on input position</returns>
        public static float updateDeepValue(Vector2 pos , CTiledMap map )
        {
            int varY = (int)map.m_vecMapPos.Y / map.m_iCellSize;
            int row = (int)pos.Y / map.m_iCellSize - varY;

            if (row < 0)
                row = 0;
            if (row > map.m_iMapHeight)
                row = map.m_iMapHeight - 1;

            float deep = 0.1f * ( row + 1 );

            return deep;
        }

        public static void updatePlayerPos(CCharacter player, CTiledMap map)
        {
            if (player.Tag == FrameWork.Tag_Manager.TagManager.TAGGED.Player)
            {
                Vector2 pos = map.convertToMapIndex(((CPlayer)player).getPos());
                if (pos.Y < 0)
                    pos.Y = 0;
                if (pos.Y > map.m_iMapHeight)
                    pos.Y = map.m_iMapHeight - 1;
                if (pos.X < 0)
                    pos.X = 0;
                if (pos.X > map.m_iMapWidth)
                    pos.X = map.m_iMapWidth - 1;
                map.playerPos = pos;
            }
        }

        #endregion
    }
}
